بوجود البرمج الضخمة والمشاريع المتقدمة استقر المبرمجون على مفهوم مكتبات الدوال مع كل لغات البرمجة والانظمة ، وفي بعض الاوقات تسمى بالـ libraries, modules, archives ، فعلى سبيل المثال في الانظمة كنظام الونيدوز تسمى بالـ dynamically linked libraries (DLLs) او الينكس المكتبات المشاركة shared libraries ، وفي البيرل توضع مجموعة مخزنة من دوال وصنف ومتغيرات في حزمة في ملف وتسمى بالوحدة البرمجية module ، ففكرة ارشفة الوحـدات في لغة البيرل لتؤفير الوقت لدى المبرمج وتزويد مجموعة أدوات لحل المشكلات المعقدة ،التي قد تكون سلبي لدى البعض فانت لاتتعلم شيء وليس هناك اي ابداع في كتابة البرنامج وتجده جاهزاً امامك لكن في بعض الحالات ليس هناك وقت للإبداع او التعلم ، والغالب اكثر المبرمجين كسالى دعني اسالك هل ستعيد ابتكار شيء ما موجود لديك باكثر كفاءة واكثر سرعة لاتفهم بشكل خاطئ اعندي ان هناك كسل جيد وكسل سئ ، فالكسل السئ يقول "سأنتظر الى شخص يفعل هذا لي" بينما الكسل الجيد يقول "لربما قد فعله شخص ما " فبعض الاشياء نريد حقاً معرفة آلية عملها مالم نعلم كيفية اعده تركيبها ،مشروع البيرل مفتوح المصدر يمكن الإطلاع على الوحـدات البرمجية الإضافية (third-party modules تعرف ايضاً add-on modules) لدى البيرل المتؤفرة في أرشيف CPAN تعرف بشبكة إرشفة البيرل الشاملة ( Comprehensive Perl Archive Network) يمكن تحميل أي وحـدة وتنزيلها على الجهاز ومطالعة البرامج هذا بإلاظافة إلى جميع شجرة مكتبة المفسر نفسة .
كل الامثله في الكتاب تستعمل وحـدات برمجية متؤفرعلى CPAN ،فكل مؤلف وحـدة يقوم بعمل لها قواعد عامة لانشاءها واستعمالها ، وسيضيفون مستند للمساعدة وتسمى بالـ(Plain Old Documentation (POD لإي ملف module's .pm .
الطريقة الاوله للمشاهده POD للوحدات على سبيل المثال وحدة Socket بتطبيق الامر:
Perldoc Socket
فهذا الامر يقوم بعرض Socket.pm's POD المحوله الى man page ، فالناتج نسبياً سيناقش من خلال الدوال والمتغيرات المعرفة في هذا الوحدة في وقت لاحق
الطريقة الاخرى لعرض المستندات هي ان تحول الـ POD الى text باستخدام
pod2text \ /usr/lib/perl5/i686-linux/5.00404/Socket.pm | more
فابرنامج pod2text يأتي مع البيرل ، وكذلك pod2html و pod2man و pod2usage و pod2latex
على الرغم من ان الوحـدة لاتقيد بنوع معين الا ان هناك بعض الطرق القياسية المصنفة من قبل مطوري لغة البيرل :
Pragmatic modules : عادة هي معلومات خاصة الى المفسر البيرل
Standard modules :تشكل اغلب وحـدات البيرل تعمل الاشياء في طريقة قياسية يحدها النظام كوحـدة Socket العالية المستوى ، والبعض منها مفيدة اثناء العمل وتعرف احياناً بالوحـدات البرمجية الوظيفية Functional modules
Extension modules : وهي الوحـدات المجتمعة مع لغة السي ( او اللغات الاخرى )
فالوحدات البرمجية الوظيفية هي فقط من تسمى بالوحـدات أو مكتبة الوحدات البرمجية فهي تشكل مجموعة من الروتنيات والمتغيرات التي تستعملها في البرنامج وماتعرف بالمكتبات او مكتبة الدوال وتشغل هذة الروتينات في وقت التشغيل run time ، بالمقابل وحدات برمجيةpragmatic تتضمن الـ pragmas التي تعدل أسلوب البيرل اثناء التجميع compile time والتي تضيف بعض القيود في الصياغة او تركيب اضافات للغة البيرل التي ليست مصممة لها ،فالـpragmatic تكتب بحروف صغير وغالباً اكثر من كلمة word long في اسمها، بينما الـمكتبات الوظيفية تستعمل حروف كبيرة وغالباً اكثر من كلمة في اسمها وتفصل بينهم بواسطة الرمز :: ، فالـ strict, vars, and warnings كلها امثلة لوحدات الـ pragmatic البرمجية التي تسعمل كثيراً في اسكربتات البيرل ، ايضاً فبعض وحدات pragmatic تؤفر الروتينات والمتغيرات التي يمكن ان تستعمل في وقت التشغيل لكن ليست الاكثر .
فالبيرل تؤفر ثلاث آليات لتضمين كود من ملف اخر وهي do, require, use ان السبب تضمين كود اخر الى برنامجك ، لتقسيم البرنامج لديك الى العديد من الملفات ليصبح اكثر سهولة لإستدعى او تضمين قطع الكود الى برنامجك عند الحاجة لهذة المهمة،وبتقسيم تخلاص من مشكلة ثقل البرنامج وسهولة التنقيح وسهولة التغير بدون التأثيرعلى البرنامج الرئيسي .
الكلمة المحجوزة do لطلب وتنفيذ الكود من الملف المحدد ،فهي اولاً سوف تضيف الاسم المدخل للملف كالـ key الى هاش %INC ، والمسار الكامل لأسم الملف كالـ value ،ثم تعيد كل الاوامر وتنفذ محتوى الملف
وبسبب ان الوحـدة ليس لديها قيمة راجعه في اي كود ، لذلك يذيل "1" الى نهاية ملف الوحـدة البرمجية أو اي قيمة صحيحة ، ولمعرفة ان الوحدة قد تم شحنها بنحاج
فعلى سبيل المثال لتضمين الملف المسمى MyDoLib.inc
#MyDoLib.inc
$author = " Yang";
print("Printing from MyDoLib.inc...\n");
sub myDoSub {
print("Printing from myDoSub()...\n");
}
1;وهنا البرنامج الرئيسي واستدعاء البرنامج الفرعي من نفس المجلد ،او بكتابة المسار الكامل للملف المراد تضمينه
#- IncDoTest.pl
do("MyDoLib.inc");
print("Printing from IncDoTest.pl...\n");
print("Author = $author\n");
myDoSub();
exit;
Printing from MyDoLib.inc... Printing from IncDoTest.pl... Author = Yang Printing from myDoSub...
فهي نسخة مطورة من do وتعمل كل شيء تفعله do لكنها تفحص اولاً الهاش %INC اذا كان الملف المحدد له موجود،فاذا كان الامر كذلك تعيد 1 ،واذا لم يكن موجود سوف يعيد خطأ بأن الملف غير متضمن ، بينما الدالة do سوف يستمر اذا لم يكن الملف متضمن ، وسوف يضاف المدخل الى %INC باسم الملف كفهرس key ،واسم المسار الكامل كالقيمة value فقط اذا التنفيذ نجح
فعلى سبيل المثال نكتب ملف اخر MyRequireLib.inc
#- MyRequireLib.inc
$author = "Dr. Yang";
print("Printing from MyRequireLib.inc...\n");
sub myRequireSub {
print("Printing from myRequireSub()...\n");
}
1;
ولستدعاء الملف
#- IncRequireTest.pl
print("Testing require()...\n");
require("MyRequireLib.inc");
print("Author = $author\n");
myRequireSub();
print("\nTesting do()...\n");
do("MyDoLib.inc");
print("Author = $author\n");
myDoSub();
exit;
Testing require()... Printing from MyRequireLib.inc... Author = Dr. Yang Printing from myRequireSub()... Testing do()... Printing from MyDoLib.inc... Author = Yang Printing from myDoSub...
ولطلب الملف المتضمن في مكتبة البيرل التي على امتداد .pm
# include the newer Getopt::Std library # (i.e. PATH/Getopt/Std.pm) require Getopt::Std; # bareword بدون علامات الاقتباس
require اولاً تسأل اذا كان الملف مشحون الى الهاش %INC ، في هذة الحالة لم يشحن بعد ، لذا المفسر سوف يبحث عن اسم الملف المطابق في مسارات المصفوفة الخاصة @INC لتضمين الكود ، في هذة الحالة وجود :: تخبر المفسر بالبحث عن الملف في مسار المصفوفة @INC على المجلد الفرعي المسمى Getopt ، فاذا فشلت عملية التجميع يظهر المفسر رسائلة الخطأ وتظهر رسائلة الخطأ بسبب اما ان يكون غير ممكن الوصول الى الملف او ان الكود يحتوي على خطأ في الصيغة او الكود لاينج القيمة true عندما ينفذ ، واذا كان غير ذلك سوف يبحث المفسر في كود الوحدة لإسم الدالة المستوردة ( فيما بعد تصدير الدوال ) ،فعندما الوحدة تستورد دالة مصدرة سابقاً فسوف يستمر التجميع في الملف الاصلى وينقل الى الامر الذي يلي use
@INC وهي مصفوفة تحتوي على المسارات الافتراضية لبحث عن الوحـدات القابلة للإستعمال مرة اخرى ، اما الهاش %INC يخزن تفاصيل الوحدات البرمجية التي تشخن وتخزن في الذاكرة ، ففي keys هي اسماء الملفات الموجودة والمشحونة والـ values هو المسار الكامل للوحدات المشحونة مع تحويل المسار من :: الى /
#!/usr/bin/perl
# INC.pl
use strict;
print "\%INC contains: \n";
foreach (keys %INC) {
print " $INC{$_}\n";
}
require File::Copy;
do '/home/perl/include.pl';
print "\n\%INC now contains: \n";
foreach (keys %INC) {
print " $INC{$_}\n";
}
ولروية المسارات الافتراضية @INC ، يمكن كتابة الامر من على سطر الاوامر باستعمال الخيار -e ، فهذا الخيار يسمح لنا بتنفيذ الكود مباشراً بدلاً من تحديد ملف البرنامج ، ولذلك لانحتاج لكتابة برنامج ثم حفظه وتنفيذ بعد ذلك ، (للوندوز الإقتباس الوحيدة ' قم بتبدلها بقتابس المزدوجة " وبدل "\n" الى \"\n\" )
>perl -e 'foreach (@INC){print $_, "\n"}'
C:\>perl -e "foreach (@INC){print $_, \"\n\"}"
البيرل بالاساس هي عملية بحث عن قائمة القياسية من المجلدات حينما نطلب منه للشحن الوحدات
use هي نسخة مطورة للـ require وتعمل كل شيء تفعله بتضمين الوحدات ، لكن هي تقدر الملف في وقت التجميع ، بدلاً من تقدير الملف في وقت التشغيل
# include Getopt::Std at run time require Getopt::Std; # include Getopt::Std at compile time use Getopt::Std;
ستناقش اكثر في آلية الاستيراد ، فعلى سبيل المثال لعرض الهاش %Config في وحدة Config الذي يخزن فيه معلومات النظام التي في الحقيقية تخزن عند تجميع وتنزيل البيرل
use Config;
# prints your operating system name.
print $Config{'osname'},"\n";
# prints the compiler which compiled perl
print $Config{'cc'},"\n";
# prints the options which were used to compile perl
print $Config{'ccflags'},"\n";
فالمحتوى الافتراضي للبحث عن المسار في المصفوفة الخاصة @INC توضع اثناء التجميع ، وعند نقل وتغير المسار الى اي مجلد فأن البيرل سوف تعيد التجميع ، يمكننا اضافة مسار الى هذة المصفوفة وازالة المسار
push @INC, "/home/lib/modules"; use CalendarModule;
على اي حال هو مشوش نوعاً ما ، لذا نستعمل lib pragma لإظافة المجلد الى المصفوفة @INC قبل اي شئ اخر والحصول عليها :
push @INC, "/home/lib/modules"; use lib CalendarModule;
او باستعمال lib مع المسار في وقتاً واحد
use lib "/home/lib/modules";
ويمكن استعمال متغير البئية
use lib "$ENV{HOME}/modules";
نستطيع اخبار البيرل بمسار الوحدة بشكل دائم باضافتة الى قائمة متغير الشل PERL5LIB:
% PERL5LIB="${PERL5LIB}:/users/damian/lib/perl5" # UNIX sh shell
% export PERL5LIB
لقد تحدثنا عن مفهوم المجال scope والبلوك من وقت الى اخر، مجال الرؤية لمتغير هو المدى الذي يمكن الوصول اليه ، فهناك في البيرل نوعين من scope للمتغيرات وهما package scope ( يسمى ايضاً بالـ dynamic scope ) ويقدر في وقت التشغيل ، بينما النوع الاخر هو lexical scope ويقدر في وقت التجميع ،ومفسر البيرل يحدد مجال الرؤية بواسطة كيفية الاعلان عن المتغيرات ، فعلى سبيل المثال
#!/usr/bin/perl –w
use strict;
my $max = 15;
sub read_max {
my $max = 5;
print " $max \n";
}
read_max();
print " $max \n";
فنلاحظ طباعة 5 بسبب اعادة تعريف المتغير الـ $max في الدالة ،لو كانت الرغبة الوصول الى المتغيرات العامة سوف نقوم بتعريف متغير Package في main ( الحزمة الافتراضية للبرنامج ) حيث متغيرات الحزمة لا تعلن مع الدالة my كما في باقي المتغيرات
#!/usr/bin/perl –w
use strict;
$main::max = 15;
sub read_max {
my $max = 5;
print " $max \t $main::max \n";
}
read_max();
print " $::max \n";
فاول متغير يعلن عنه كمتغيرعام ويسمى بالـمتغير package ، بينما باقي المتغيرات تعلن مع الدالة my وتسمى بالـمتغرات lexical ويتم حسابها في وقت التجميع ، فمتغير الحزمة يصرح عنه في حزمة main ويمكننا الوصول الى ذلك المتغير بواسطة كتابة فضاء الاسم main (عند عدم كتابة فضاء الاسم سوف يأخذ الحزمة الافتراضية ) مع استعمال المؤثر :: واسم المتغير المراد الوصول اليه ، والشيء المهم التعريف بواسطة use strict او use strict 'vars' لفحص المتغيرات المصرحة مع lexical وفحص المتغيرات المصرحة مع package وذلك في وقت التجميع ، فأما بمنع تعريف المتغير قبل التصريح عنه وذلك لإنشاء متغير جديد بنفس الاسم بدلاً من تعديل المتغير الموجود أو بالوصول الى المتغير العام الذي تم تعريفة ، فهذا الشيء يصبح مهماً عند تقسيم البرنامج الى عددة ملفات ،كما سبق في مصطلح المجال scope هناك نوعين من المتغيرات متغيرات الحزمة التي هي مريئة في اي مكان في البرنامج ، والمتغيرات lexical التي المجال و الرؤية مقيدة في ذلك الملف او البلوك عند اعلانهم
#!/usr/bin/perl –w
use strict;
my $max = 15;
sub read_max {
$main::max = 5;
print " $max \t $main::max \n";
}
read_max();
print " $max \t $::max \n";
فعندما strict ممكنة لانستطيع انشاء متغير package ، ولكي نعرف متغير حزمة فاما بالطريقة السابقة او باستعمال vars pragma ( اقدم ) او باستعمال الكلمة المحجوزة our ( الاحدث )
use vars '$max'; # older $max=15;
فهي تخبر البيرل بجعل اسم المتغير كامتغير حزمة ، فهذا الاستعمال لايفيد عندما متغيرات strict غير ممكنة ولايمكن ان تقوم بالتخصيص والاعلان في نفس الوقت
الكلمة المحجوزة our للإعلان عن متغير حزمة لكن يجعله في رؤية lexically مثل الكلمة المحجوزة my
our $max=15; #modern
واذا كانت الرغبة بتغير الاسم الافتراضي بستخدام الكلمة المحجوزة package لنشاء فضاء اسم جديد التي تتضمن مجموعة من المتغيرات والدوال ... الخ ، وتسمى تلك المجموعة بأسم معين لكي يتم الوصول اليه فيما بعد ،ففضاء الاسم Name Space هو ليس متغيراو دالة او class ، هو فقط ذلك الاسم لمقطع الكود الذي سنشير الية لاحقاً بواسطة هذا الاسم
صيغة تعريف فضاء اسم جديد :
package name_space;
القواعد الاساسية للـ name spaces :
الاسم الافتراضي لفضاء الاسم تسمى بالـ "main" ، لذلك كل سطر في البرنامج هو في main
عند تعريف package name_space; جديدة مرة اخرى ،سيتم الخروج من فضاء الاسم الحالية والدخول الى هذة name_space
وعند اضافة بلوك للحزمة الحالية واظافة فضاء اسم جديد ،فأن نهاية الحزمة الجديدة على نهاية بلوك الكود الحالي ، او نهاية الملف الحالي
المعرفات كالمتغيرات والدوال في الحزمة يمكن الوصول اليه بواسطة :: مع اضافة اسم الحزمة
الكلامة المحجوزة __PACKAGE__ تحتوي على اسم الحزمة او فضاء الاسم الحالي ، ومن خلال __LINE__ ليجاد رقم السطر في البرنامج و __FILE__ للملف الحالي
ولتحقق من القواعد :
# NameSpaceTest.pl
# starting with main
$j = "Java";
$f = "Fortran";
$p = "Perl";
$c = "C++";
@list = ($j, $f, $p, $c);
print("In ",__PACKAGE__,"...\n");
print("list = ",join(' , ',@list),"\n");
package Calendar;
# entering Calendar
$j = "January";
$f = "February";
$m = "March";
$a = "April";
@list = ($j, $f, $m, $a);
print("In ",__PACKAGE__,"...\n");
print("list = ",join(' , ',@list),"\n");
{
package Fruit;
# entering Fruit
$p = "peach";
$o = "orange";
$m = "mango";
$a = "apple";
@list = ($p, $o, $m, $a);
print("\nIn ",__PACKAGE__,"...\n");
print("list = ",join(' , ',@list),"\n");
}
# ending Fruit
# back to Calendar
print("\nBack in ",__PACKAGE__,"...\n");
print("list = ",join(' , ',@list),"\n");
package Fruit;
# entering Fruit
print("\nBack in ",__PACKAGE__,"...\n");
print("main::p = ",$main::p,"\n");
print("Fruit::p = ",$Fruit::p,"\n");
print("list = ",join(' , ',@list),"\n");
print("main::list = ",join(' , ',@main::list),"\n");
print("Calendar::list = ",join(' , ',@Calendar::list),"\n");
exit;In main... list = Java , Fortran , Perl , C++ In Calendar... list = January , February , March , April In Fruit... list = peach , orange , mango , apple Back in Calendar... list = January , February , March , April Back in Fruit... main::p = Perl Fruit::p = peach list = peach , orange , mango , apple main::list = Java , Fortran , Perl , C++ Calendar::list = January , February , March , April
مع strict المفسر يمنع التخصيص ويحدث خطأ bugs ويسمى fragment
#!/usr/bin/perl -w use strict; package MyPackage; $package_variable = "This variable is in 'MyPackage'"; # ERROR
ويعرض الخطأ
Global symbol "$package_variable" requires explicit package name at…
ولتجاوز هذة المشكلة وضع الدالة my للمتغير ،لكي تعيده الى مجال رؤية على مستوى الملفlexical مايسمى file-scoped lexical ، على اي حال اذا اردت تعريف متغير عام مع strict لابد ان يكون بشكل واضح
#!/usr/bin/perl -w use strict; package MyPackage; my $package_variable = 5; # file-global lexical variable print $package_variable."\n"; package main; #print $MyPackage::package_variable."\n"; #لايسمح print $package_variable."\n"; $MyPackage::package_variable = 15; #تعريف متغير حزمة print $MyPackage::package_variable."\n"; package cand; print $MyPackage::package_variable."\n"; # الوصول الى متغير الحزمة print $package_variable."\n"; # الوصول الى متغير الملف
ان تضمين my في الحزمة تعيد متغير الحزمة الى مجال رؤية الملف lexical ، فبدلاُ منه الكلمة المحجوزة our لتصريح عن متغير حزمة .
بإمكان تضمين متغيرات الحزمة كالمتغيرات محلية داخل دالة اوبلوك بواسطة الكلمة المحجوزة local ،فهي تخفي متغير الحزمة الموجود بواسطة وضع قيمة مؤقتة ،وسوف يبقى موجود طالما ينفذ الدالة او البلوك ثم تحرر عن الخروج
local $hero; # تعريف متغير محلي
local ($zip, @boing, %yatatata); # تعريف اكثر من واحد
local @list = (1, 2, 3, 4);
local ($red, $blue, $green, $yellow) = ("red", "blue", "green");
فالكلمة المحجوزة local ليست كالـ my التي تنشاء متغير محلي جديد ، هي فقط عملية على متغير موجود الذي يمكن ان يكون متغير عام او متغير معرف في استدعاء السياق ، واذا المتغير ليس موجوداً وكانت strict ممكنة البيرل ستحدث خطاً في وقت التجميع
#!/usr/bin/perl
# localelement.pl
our @array=(1,2,3);
{
local $array[1]=4;
print @array,"\n"; # produces '143'
}
print @array,"\n", # produces '123'
لذا يمكن الاعلان عن package variables lexically مع our ، او متغيرحزمة مخفي مع اخر باستعمال local التي تختلف عن my لان رؤيتة يستمر الى ان يغادر بلوك التنفيذ ، حيث تقوم بعمل متغير حزمة مؤقت ومخفي مع المتغير الاخر بنفس الاسم لكن تحتفظ بالقيمة المختلفة والشكل يوضح آلية العمل
لتوضيح عمل local و لمعرفة الاختلافات بين my, our, use vars
#!/usr/bin/perl
# scope-our.pl
use warnings;
use strict;
package MyPackage;
my $my_var = "my-var"; # file-global lexical variable
our $our_var = "our-var"; # global to be localized with 'our'
our $local_var = "global-var"; # global to be localized with 'local'
use vars qw($use_var); # define 'MyPackage::use_var' which exists
# only in this package
$use_var = "use-var";
package AnotherPackage;
print "Outside, my_var is '$my_var' \n"; # display 'my-var'
print "Outside, our_var is '$our_var' \n"; # display 'our-var'
print "Outside, local_var is '$local_var' \n"; # display 'global-var'
#-----
sub sub1 {
my $my_var = "my_in_sub1";
our $our_var = "our_in_sub1";
local $local_var = "local_in_sub1";
print "In sub1, my_var is '$my_var'\n"; # display 'my_in_sub1'
print "In sub1, our_var is '$our_var'\n"; # display 'our_in_sub1'
print "In sub1, local_var is '$local_var'\n"; # display 'local_in_sub1'
sub2();
}
sub sub2 {
print "In sub2, my_var is '$my_var'\n"; # display 'my-var'
print "In sub2, our_var is '$our_var'\n"; # display 'our-var'
print "In sub2, local_var is '$local_var'\n"; # display 'local_in_sub1'
}
#-----
sub1();
print "Again outside, my_var is '$my_var' \n"; # display 'my-var'
print "Again outside, our_var is '$our_var' \n"; # display 'our-var'
print "Again outside, local_var is '$local_var' \n"; # display 'global-var'
في المثال السابق ليس هناك سبب لاستعمال our في هذة الحالة وبامكان استعمال الدالة my لإنشاء المتغير بدلاًُ منه ولتؤضيح أكثر شاهد المثال التالي
#!/usr/bin/perl
# scope-my.pl
use warnings;
use strict;
my $file_scope = "visible anywhere in the file";
print $file_scope, "\n";
sub topsub {
my $top_scope = "visible in 'topsub'";
if (1 > 0.5) {
my $if_scope = "visible inside 'if'";
# $file_scope, $top_scope, $if_scope ok here
print "$file_scope, $top_scope, $if_scope \n";
}
bottomsub();
# $file_scope, $top_scope ok here
print "$file_scope, $top_scope\n";
}
sub bottomsub {
my $bottom_scope = "visible in 'bottomsub'";
# $file_scope, $bottom_scope ok here
print "$file_scope, $bottom_scope \n";
}
topsub();
# only $file_scope ok here
print $file_scope, "\n";
مراجع الدوال المخفية هي كأي متغيرات اخرى لذلك هي ليست في مجال رؤية الحزمة package scope ، بينما دوال المستخدم تكون دائماً في مجال رؤية الحزمة لذا نستطيع الوصول الى اي دالة في حزمة اخرى على خلاف الدوال المخفية ، ايضاً المتغيرات الخاصة التي لايمكن ادركها كالـ $_, @_, %ENV, @ARGS .. الى غير ذلك هي متغيرات حزمة في main package |
مفهوم فضاء الأسم namespaces هي لمختلف الاسماء(كالمتغيرات والمصفوفات والدوال..الخ)، فالأصناف والمصفوفات في هياكل البيرل لاتخلط ببساطة معاً بل توزع على شكل مجموعات هرمية تسمى namespaces ، فعلى سبيل المثال فضاء الأسم لمتغير محمد يميز عن غيرة من المتغيرات المسمى أحمد وصالح ، وجمعت المتغيرات احمد ومحمد تحت اسم العائلة (اسم الحزمة ) وينتمون الى هذة العائلة بينما المتغير صالح له اسم حزمة اخرى ، لكل حزمة او موديل او صنف لها symbol table خاص بها وهذا الجدول نسبياً يحتوي على كل الاسماء المنتمون الى هذة الحزمة ، وفي البيرل هو الهاش الذي يحتفظ به كل الاسماء المعرفة في الحزمة وتخزن فيه اسماء متغيرات الحزمة ، وهذا الهاش يسمى بنفس اسم الحزمة مع الرمز :: ، فعلى سبيل المثال جدول الترميز لحزمة MyPackage هو %MyPackage::
sub dispSymbols {
my($hashRef) = shift;
my(%symbols);
my(@symbols);
%symbols = %{$hashRef};
@symbols = sort(keys(%symbols));
foreach (@symbols) {
printf("%-10.10s| %s\n", $_, $symbols{$_});
}
}
dispSymbols(\%MyPackage::);
#same: dispSymbols(*MyPackage::{HASH});
#same: dispSymbols(*MyPackage::);
package MyPackage;
$bar = 2;
sub baz {
$bar++;
}bar | *MyPackage::bar baz | *MyPackage::baz
هناك اسمين في جدول الترميز %MyPackage:: ،واذا عرضت جدول الترميز %main:: تجد فيه اكثر من 40 اسم كالـ STDIN, STDOUT, STDERR, @ARGV, @ARGVOUT, %ENV, @Inc, %SIG .....
الحزمة الافتراضية وهي root او الجذر لكل جداول الترميز المتدرجة ، لذا اي متغير يعلان بدون تضمينة الى حزمة اخرى سوف تاخذة هذة الحزمة
our $scalar; # defines $main::scalar
ولكون ان main هي الجدول الجذر لكل جداول الترميز ، لذا التالي كلها متكافئة :
package MyPackage; our $scalar; package main::MyPackage; our $scalar; our $MyPackage::scalar; # متضمن جذره our $main::MyPackage::scalar; # بشكل وضح
بسبب ان كل حزمة يجب ان يكون لديها main معرفة كجذر ، فالحزمة main تعرف المدخل في جدول الترميز ، والتالي مكافئة ايضاًُ لما سبق بشكل اغرب مما سبق :
our $main::main::main::main::main::main::main::MyPackage::scalar;
فهذة الحقيقة مفيدة في البيرل وهي اكثر نقطة للتفصيل اسفل،لكن اذا استعملنا السكربت السابق للعبور جدول الترميز ثم نحتاج في هذة الحالة الخاصة الى النظر خارج الية
dispSymbols(\%MyPackage::); $main::main::main::main::main::main::main::MyPackage::scalar=5; print $MyPackage::scalar; package MyPackage;
حينما عرفنا متغير حزمة جديد في حزمة جديدة فأن البيرل تقوم باضافة جداول ترميز لتحتفظ بالمتغير،حيث ان الحزمة تفصل بواسطة :: بصيغة مشابة للمجلدات المفصولة بواسطة / او \ وايضاً مشابة للاسم الـdomain بواسطة dot ، ولنفس السبب الرمز :: تعرف الموقع في نظام التسمية المتدرج ، فعلى سبيل المثال اذا صرحنا عن حزمة بثلاثة عناصر للحزمة فان البيرل تضيف ثلاثة جداول ترميز ، وكل واحد يحتوي على المدخل الى هذة الحزمة .
package World::Country::City; our $variable = "value";
يضيف سلسة من الجدوال وكل جدول منهم يشير الى الاخر (ولدية هاش خاص به)، فجدول الترميز World منشأ كمدخل من main ، هذا يعني ان جدول الترميز main سوف يحتوي على مدخل entryالى جدول الترميز World والذي لان يحتوي على اي متغيرات فعلية سواء مدخل جدول الترميز Country الذي لة الاسم World::Country ، وبالعودة الى Country فهو يحتوي مدخل جدول الترميز City ، والـ City لاتحتوي على اي من مدخلات الجدول الترميز لكنة يحتوي المدخل الـ typeglob المسمى *variable ، التي يحوي مرجع المتغير الى القيمة ، وعندما نضع كلهم مع بعض سوف يعطينا متغير الحزمة :
$main::World::Country::City::variable;
ولكون الـ main هو الروت من شجرة الجدول الترميز لانجتاج الى التحديد بشكل واضح ، في الحقيقة هو عادتاً فقط يحضر كنتيجة من انشاء مرجع رمزي الى الـtypeglob كمارينا سابقاً ولذا يمكن ان نقول :
$World::Country::City::variable;
وهو الاسم الكامل لمتغير الحزمة
كل المتغيرات العامة متغيرات حزمة في الحزمة main والتي هم في الشق الـtypeglob مع ذلك الاسم في جدول الترميز ، فعلى سبيل المثال الشكلين متماثلين للدالة التي هي فقط مراجع كود code references مخزنة في typeglobs .وبدون الاعلان عن حزمة جديدة فالـ typeglob يعوض بشكل آلي في حزمة main ولذا التالي متشابهان :
*subglob = \&mysubroutine; *main::subglob = \&main::mysubroutine;
الان يمكن الاستدعاء الدالة mysubroutine باسم او بتضمين main:: اليها :
mysubroutine(); # original name subglob(); # aliased name
ويمكن بالطبع عمل alias لمختلف الحزم ايضاً وذلك بعمل دالة معرفة في حزمة مرئية للاخرى ، بهذا العمل فان البيرل تعمل بواسطة آلية الإستيراد حيث تقوم باستعمال الموديل والحصول على الدالة التي يمكن استدعاها بدون استعمال الموديل في برنامجنا :
# import 'subroutine' into our namespace *main::subroutine = \&My::Module::subroutine;
فالـ Typeglob يخصص ويعمل لإي نوع من انواع الشقوق التي تحتويها ، كمقابض الملفات والمراجع code ، ولذا نحن يمكن ان نشاء اسم مستعار لمقبض الملف في هذة الطريقة :
# OUT = STDOUT; لانستطيع تخصيص *OUT = *STDOUT; print *OUT; # produces '*main::STDOUT' print OUT "this goes to standard output";
فهذا الشيء يدعنا لفعل الاشياء مثلاً نختار من عدة خيارات لدالة على وقت التشغيل :
# choose a subroutine to call and alias it to a local name
our *aliassub = $debug? *mydebugsub: *mysubroutine;
# call the chosen subroutine via its alias
aliassub("Testing", 1, 2, 3);
وكل هذة الاعمال بسبب ان حزمة typeglobs هي مدخلات في جدول الترميز نفسة ، وكل الاشياء الاخرى محفوظة في الـ typeglob بنفس الاسم ، فطالما نحن نعرف كيف يعمل الكل ، فهو سهل نسبياً للرؤية كيف البيرل تعمل الشيء بنفسها ، على سبيل المثال عندما نعرف اسم دالة سوف نجعل البيرل هنا لإنشاء مرجع code ثم يخصص الى الـ typeglob من ذلك الاسم في جدول الترميز ، وهنا طريقة لتعريق الدالة :
#!/usr/bin/perl
# anonsub.pl
use warnings;
use strict;
our $anonsub = sub {print "Hello World\n"};
*namedsub = \&{$anonsub};
namedsub();
كتبنا نفس الوظيفة كما لو عرفنا اسم دالة لكن بشكل واضح ، فاولاً قمنا بانشاء مرجع كود ثم تخصيص مرجع الكود الى الـtypeglob ،فالدالة معرفة في كود بدلاُ من الاعلان لكن التاثير نفسه ،ويمكننا انشاء اسم مستعار الى متغيرات aliases to scalars ( ومصفوفات وهاشات ) في طريقة مشابة ،كالمثال المشاهد اسفل مع المتغيرات تشير الى نفس التخزين :
#!/usr/bin/perl -w # scalaralias.pl use strict; our $scalar1="one"; *scalar2=\$scalar1; our $scalar2="two"; print $scalar1; # produces "two";
استعمالنا our لتصريح عن المتغيرات لكي تحفظ مع strict في البيرل ، فمع my لان يقوم بمعالجة جدول الترميز ، وايضاً نلاحظ تخصيص الـtypeglob الذي لايطلب اي تصريح له ثم بعد ذلك لكي نصل الى المتغير في ذلك typeglob نصرح عن $scalar2 بعده ،والتي لانحتاج الى تضمين main:: ، لكن مع مختلف الاستعمالات السابق وبمختلف الوصول للمتغير scalar المعلان كمتغير حزمة
use strict;
our $scalar="text";
*glob = \$scalar;
our $glob="newtext";
print $main::glob," ",$glob," ",${*glob}," ",$scalar," ",$main::scalar,"\n";
print *glob," ",*main::glob,"\n";
print *glob{SCALAR}," ",*main::glob{SCALAR}," ",\$scalar," ",\$main::glob;
*glob2 = *glob;
print *glob2; #produces '*main::glob'
ملاحظة : تخصيص مرجع لقيمة مخفية الى الـ typeglob لتعريف عن ثابت
use warnings; use strict; our $PI; # declare use of package scalar *PI=\"immutable"; # assign constant string to scalar slot of glob print $PI; # produces 'immutable' $PI='no!'; # Error! 'Attempt to modify constant scalar...'
فهذا يسبب بانشاء المتغير $PI لكون أن التخصيص مرجع الى المتغير ، ولان المرجع قيمة بدلاُ من المتغير فلايمكن اعادة تعريفة ولذا فأن المتغير $PI للقراءة فقط
لذا علينا الحذر عن تخصيص الاشياء الى typeglob كالمراجع او القيمة او typeglobs اخرى ، فعلى سبيل المثال تخصيص نصوص مثيرة للإهتمام بتأثير غير متوقع
print *hello; # produce '*main::hello' *hello = "world"; print *hello; # produce '*main::world'
فاذا حاولنا طباعة $hello ستظهر رسائلة بان المتغير غير موجود ، لكن في الاخير عند طباعة *hello نجد انه اصبح اسم مستعار بدلاً منه ، فالنصوص هنا تأخذ مرجع رمزي الى اسم typeglob او symbolic reference to a typeglob name وهي تكافئ الصيغة الفعلية :
*hello = *world;
على اي حال المراجع الرمزية هي مصدر للتشوش في الكود وبسهولة نسيانها بعدم تضمين المرجع بواسطة \ ، ويسبب عند تخصيص مصفوفات اوالهاشات اليه بتقديرة في scalar context والاسم المستعار الى typeglob الى typeglob اخر سيكون الرقم او عدد عناصر المصفوفة :
@array = (1, 2, 3); *hello = @array; print *hello; # produces 'main::3' since @array has three elements
وبتخصيص اسم مستعار للدالة،تستدعى الدالة ويخصص الـtypeglob المستعار بالقيمة المعادة من الدالة
*hello = subroutine_that_returns_name_to_alias_to(@args);
ولكون جداول الترميز هو هاشات ، وتلك الهاشات تخزن فيها typeglob بنفس الاسم ، يمكننا الوصول اليه من خلال %{*main::}
#!/usr/bin/perl
# dumpmain.pl
use warnings;
use strict;
foreach my $name (sort keys %{*::}) {
next if $name eq 'main';
print "Symbol '$name' => \n";
# extract the glob reference
my $globref = ${*::} {$name};
# define local package variables through alias
local *entry = *{$globref};
# make sure we can access them in 'strict' mode
our ($entry, @entry, %entry);
# extract scalar, array, and hash via alias
print "\tScalar: $entry \n" if defined $entry;
print "\tArray : [@entry] \n" if @entry;
print "\tHash : {", join(" ", {%entry}), "} \n" if %entry;
# check for subroutine and handle via glob
print "\tSub '$name' defined \n" if *entry{CODE};
print "\tHandle '$name' (", fileno(*entry), ") defined \n"
if *entry{IO};
}
البيرل تعرف اربع دوال خاصة ، و تنفذ آلياً بواسطة تجميع وتنفيذ العمليات ، والكلمة المحجوزة sub هي خيارية عند تعريفهم:
الدالة BEGIN() تستدعى في وقت التجميع مباشراًُ بعد تعرفيها ،وبعد تنفذيها فان سوف تحذف وتصبح غير معرفة ، واذا البرنامج لديه عدة دوال من BEGIN() سوف تنفذ في الطلب "first in, first out"
الدالة CHECK() تستدعي في نهاية عملية التجميع ، واذا هناك عدة دوال سوف تستدعى بالطلب "last in, first out" أي سوف يبداء المفسر بتنفيذ الدوال عكسياً والدالة التي تعرف اخراً تنفذ اولاً
الدالة INIT() تستدعى في بداية عملية التنفيذ ، واذا هناك عدة دوال سوف تستدعى بالطلب "first in, first out"
الدالة END() تستدعى في نهاية عملية التنفيذ واذا هناك عدة دوال سوف تستدعى بالطلب "last in, first out"
#- BeginEndTest.pl
$f = "Fortran";
print("Printing before the first BEGIN() code...\n");
sub BEGIN {
print("In BEGIN: name space = ",__PACKAGE__,"\n");
print("In BEGIN: \$f = $f...\n");
}
sub CHECK {
print("In CHECK: name space = ",__PACKAGE__,"\n");
print("In CHECK: \$f = $f...\n");
}
sub INIT {
print("In INIT: name space = ",__PACKAGE__,"\n");
print("In INIT: \$f = $f...\n");
}
sub END {
print("In END: name space = ",__PACKAGE__,"\n");
print("In END: \$f = $f...\n");
}
print("Printing after the first END() code...\n");
package MyPackage;
print("Printing before the second BEGIN() code...\n");
sub BEGIN {
print("In BEGIN: name space = ",__PACKAGE__,"\n");
print("In BEGIN: \$main::f = $main::f...\n");
}
sub CHECK {
print("In CHECK: name space = ",__PACKAGE__,"\n");
print("In CHECK: \$main::f = $main::f...\n");
}
sub INIT {
print("In INIT: name space = ",__PACKAGE__,"\n");
print("In INIT: \$main::f = $main::f...\n");
}
sub END {
print("In END: name space = ",__PACKAGE__,"\n");
print("In END: \$main::f = $main::f...\n");
}
print("Printing after the second END() code...\n");
exit;In BEGIN: name space = main In BEGIN: $f = ... In BEGIN: name space = MyPackage In BEGIN: $main::f = ... In CHECK: name space = MyPackage In CHECK: $main::f = ... In CHECK: name space = main In CHECK: $f = ... In INIT: name space = main In INIT: $f = ... In INIT: name space = MyPackage In INIT: $main::f = ... Printing before the first BEGIN() code... Printing after the first END() code... Printing before the second BEGIN() code... Printing after the second END() code... In END: name space = MyPackage In END: $main::f = Fortran... In END: name space = main In END: $f = Fortran...
وحـدات البيرل البرمجية هي ملف خاص تحتوي على متغيرات والدوال وتودي مهمة معينة ،وبامكان تضمين ملف او اكثر فيها ، و لها اسم خاص space name ، والاسم الخاص يطابق اسم الملف، وامتدادها .pm فعلى سبيل المثال
#- MyModule.pm
package MyModule;
sub BEGIN {
print("Printing in BEGIN of ",__PACKAGE__,"...\n");
$begin = "Begin";
}
sub CHECK {
print("Printing in CHECK of ",__PACKAGE__,"...\n");
$check = "Check";
}
sub INIT {
print("Printing in INIT of ",__PACKAGE__,"...\n");
$init = "Init";
}
sub END {
print("Printing in END of ",__PACKAGE__,"...\n");
$end = "End";
}
print("Printing from MyModule.pm...\n");
sub myModuleSub {
print("Printing from myModuleSub()...\n"); }
1;لتجربة الوحـدة
#ModuleTest.pl
print("Printing from ModuleTest.pl before MyModule...\n");
require MyModule;
print("Printing from ModuleTest.pl after MyModule...\n");
print("MyModule::begin = $MyModule::begin\n");
print("MyModule::check = $MyModule::check\n");
print("MyModule::init = $MyModule::init\n");
print("MyModule::end = $MyModule::end\n");
MyModule::myModuleSub();
exit;
Printing from ModuleTest.pl before MyModule... Printing in BEGIN of MyModule... Printing from MyModule.pm... Printing from ModuleTest.pl after MyModule... MyModule::begin = Begin MyModule::check = MyModule::init = MyModule::end = Printing from myModuleSub()... Printing in END of MyModule...
ومن الملاحظ في الخرج الدالتين CHECK, INIT لاتنفذ، اما الدالة BEGIN تنفذ اثناء عملية التجميع MyModule.pm وليس في اثناء عملية تجميع ModuleTest.pl ، وايضاً في الخرج الدالة END لاتنفذ في نهاية عملية تنفيذ MyModule.pm ولكن في نهاية عملية تنفيذ ModuleTest.pl
بعد معرفة الأفكار الاساسية سنقوم بكتابة الوحـدة البرمجية المسمى Geneticcode.pm التي تتضمن شفرة الحمض النووي DNA codons الى الأحماض الأمينية وبعد ذلك يتم ترجمت نصوص بيانات الحمض النووي DNA الى الجزء البروتني
في البداية سوف ننشاء الملف Geneticcode.pm وندخل فيها الهاش المسمى %genetic_code وملئ الهاش بخارطة الشفرات codons الى الأحماض الأمينية ، وتضمين الدالة codon2aa التي تسخدم الهاش لترجمة وسطاء الـcodon الى الأحماض الأمينية وتعيد القيم الى البرنامج الرئيسي
#Geneticcode.pm
package Geneticcode;
use strict;
use warnings;
my(%genetic_code) = (
'TCA' => 'S', # Serine
'TCC' => 'S', # Serine
'TCG' => 'S', # Serine
'TCT' => 'S', # Serine
'TTC' => 'F', # Phenylalanine
'TTT' => 'F', # Phenylalanine
'TTA' => 'L', # Leucine
'TTG' => 'L', # Leucine
'TAC' => 'Y', # Tyrosine
'TAT' => 'Y', # Tyrosine
'TAA' => '_', # Stop
'TAG' => '_', # Stop
'TGC' => 'C', # Cysteine
'TGT' => 'C', # Cysteine
'TGA' => '_', # Stop
'TGG' => 'W', # Tryptophan
'CTA' => 'L', # Leucine
'CTC' => 'L', # Leucine
'CTG' => 'L', # Leucine
'CTT' => 'L', # Leucine
'CCA' => 'P', # Proline
'CCC' => 'P', # Proline
'CCG' => 'P', # Proline
'CCT' => 'P', # Proline
'CAC' => 'H', # Histidine
'CAT' => 'H', # Histidine
'CAA' => 'Q', # Glutamine
'CAG' => 'Q', # Glutamine
'CGA' => 'R', # Arginine
'CGC' => 'R', # Arginine
'CGG' => 'R', # Arginine
'CGT' => 'R', # Arginine
'ATA' => 'I', # Isoleucine
'ATC' => 'I', # Isoleucine
'ATT' => 'I', # Isoleucine
'ATG' => 'M', # Methionine
'ACA' => 'T', # Threonine
'ACC' => 'T', # Threonine
'ACG' => 'T', # Threonine
'ACT' => 'T', # Threonine
'AAC' => 'N', # Asparagine
'AAT' => 'N', # Asparagine
'AAA' => 'K', # Lysine
'AAG' => 'K', # Lysine
'AGC' => 'S', # Serine
'AGT' => 'S', # Serine
'AGA' => 'R', # Arginine
'AGG' => 'R', # Arginine
'GTA' => 'V', # Valine
'GTC' => 'V', # Valine
'GTG' => 'V', # Valine
'GTT' => 'V', # Valine
'GCA' => 'A', # Alanine
'GCC' => 'A', # Alanine
'GCG' => 'A', # Alanine
'GCT' => 'A', # Alanine
'GAC' => 'D', # Aspartic Acid
'GAT' => 'D', # Aspartic Acid
'GAA' => 'E', # Glutamic Acid
'GAG' => 'E', # Glutamic Acid
'GGA' => 'G', # Glycine
'GGC' => 'G', # Glycine
'GGG' => 'G', # Glycine
'GGT' => 'G', # Glycine
);
#
# codon2aa
#
# A subroutine to translate a DNA 3-character codon to an amino acid
# Version 3, using hash lookup
sub codon2aa {
my($codon) = @_;
$codon = uc $codon;
if(exists $genetic_code{$codon}) {
return $genetic_code{$codon};
}else{
die "Bad codon '$codon'!!\n";
}
}
1;قواعد strict مستخدمة وايضاً رسائل التحذير من خلال warnings في جميع اكواد الوحدة، وفي الاخير تم تعريف دالة codon2aa فهي تأخذ الـcodon الممثلة كانص للثلاثة DNA الاساسية ويعاد الأحماض الأمينية التي تقابل الـ codon وبواسطة الدالة uc() لإعادةالنصوص الى الحروف الكبيرة وفحص الهاش اذا كان موجود باستخدام الدالة الداخلية exists ثم اعادة الناتج الى البرنامج الرئيسي وان لم تكن الشفره codon موجودة فالدالة تعيد die للخروج من البرنامج وتنهي برسالة الخطأ
والبرنامج الرئيسي
use strict;
use warnings;
use lib "/home/tisdall/MasteringPerlBio/development/lib";
use Geneticcode;
my $dna = 'AACCTTCCTTCCGGAAGAGAG';
# Initialize variables
my $protein = '';
# Translate each three-base codon to an amino acid, and append to a protein
for(my $i=0; $i < (length($dna) - 2) ; $i += 3) {
$protein .= Geneticcode::codon2aa( substr($dna,$i,3) );
}
print $protein, "\n";الدالة الداخلية substr لإخذ جزء من النص المشار ، فهنا اخذ ثلاثة حروف من المتغير $dna بداية من الموقع المعطى في المتغير $i وتعبر الى الدالة codon2aa
NLPSGRE